package com.specter.sure.core.impl;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.specter.sure.core.AuthConfig;
import com.specter.sure.core.StokenizorProvider;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.StringUtils;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
public class StokenizorProviderImpl implements StokenizorProvider, InitializingBean {
	
	private RedisTemplate<Object, Object> redisTemplate;
	private @Autowired AuthConfig PROP;

	protected static final AtomicBoolean IS_INIT_REDIS_SSO = new AtomicBoolean(false);

	private static final String ISSUER = "liangthink";

	// JWT格式：header.payload.signature 三个部分，用.分隔

	// header格式：{ "alg": "HS256", "typ": "JWT" } 的Base64加密
	/*
	 * payload格式：{ "reg": "注册部分", "pub": "公共部分", "pri": "私有部分"} 的Base64加密
	 * Registered部分: {"iss":"签发者","sub":"用户者","aud":"接收者","exp":"过期时间","iat":"签发时间"}
	 */
	// signature格式：HMACSHA256(Base64(header).Base64(payload),"your-secret-key")

	@Override
	public void set(Object key, Object value) {
		redisTemplate.opsForValue().set(key, value);
	}

	@Override
	public void set(Object key, Object value, long expire) {
		redisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS);
	}

	@Override
	@SuppressWarnings("unchecked")
	public <T> T get(Object key, Class<T> type) {
		Object value = redisTemplate.opsForValue().get(key);
		if (value != null && type != null && !type.isInstance(value)) {
			log.error("Cached value is not of required type [{}]: ", value);
			throw new IllegalStateException("Cached value is not of required type [" + type.getName() + "]: " + value);
		}
		return (T) value;
	}

	@Override
	public <T> T get(Object key, Class<T> type, long expire) {
		T value = this.get(key, type);
		this.set(key, value, expire);
		return value;
	}

	@Override
	public boolean valid(Object key) {
		boolean k = redisTemplate.hasKey(key);
		boolean v = k && (get(key, Object.class) != null);
		return k && v;
	}

	@Override
	public boolean valid(Object key, Object value) {
		boolean k = redisTemplate.hasKey(key);
		Object v = k ? get(key, Object.class) : null;
		return k && value.equals(v);
	}

	@Override
	public void evict(Object key) {
		redisTemplate.delete(key);
	}

	@Override
	public String ticketCreate(String token) {
		String ticket = UUID.randomUUID().toString();
		this.set(ticket, token, 60 * 60 * 2);
		return ticket;
	}

	@Override
	public String ticketValidate(String ticket) {
		String token = this.get(ticket, String.class);
		this.evict(ticket);// 用完即删除
		return token;
	}

	@Override
	public String tokenCreate(Map<String, String> claims, long expire) {
		Algorithm algorithm = Algorithm.HMAC256(PROP.STORAGE_TOKEN_SECRET);
		// 设置过期时间为2小时
		JWTCreator.Builder builder = JWT.create().withIssuer(ISSUER).withExpiresAt(DateUtils.addSeconds(new Date(), (int) expire));

		claims.forEach(builder::withClaim);
		return builder.sign(algorithm);

	}

	@Override
	public Map<String, String> tokenValidate(String token) {
		if (token == null || "".equals(token)) {
			return null;
		}
		try {
			Algorithm algorithm = Algorithm.HMAC256(PROP.STORAGE_TOKEN_SECRET);
			DecodedJWT jwt = JWT.require(algorithm).withIssuer(ISSUER).build().verify(token);

			String header = StringUtils.newStringUtf8(Base64.decodeBase64(jwt.getHeader()));
			String payload = StringUtils.newStringUtf8(Base64.decodeBase64(jwt.getPayload()));

			Map<String, String> map = null;
			if (header != null && payload != null) {
				map = new HashMap<>(2);
				map.put("header", header);
				map.put("payload", payload);
			}
			return map;
		} catch (Exception e) {
			return null;
		}
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		IS_INIT_REDIS_SSO.set(true);
	}

	/* 设置KEY序列化值 */
	@Autowired(required = false)
	public void setRedisTemplate(RedisTemplate<Object, Object> redisTemplate) {
		RedisSerializer<?> srs = new StringRedisSerializer();
		redisTemplate.setKeySerializer(srs);
		redisTemplate.setHashKeySerializer(srs);
		this.redisTemplate = redisTemplate;
	}

	@Override
	public String stokenCreate(String token) {
		return DigestUtils.md5Hex(new StringBuilder().append(token).toString());
	}

	@Override
	public String stokenToKey(String sid, String key) {
		return sid + "/" + key;
	}
}
